﻿
namespace EasyStep.TestEditor.Controls
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Drawing;
    using System.Text.RegularExpressions;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Drawing.Drawing2D;
    using System.Threading.Tasks;
    using log4net;
    using System.ComponentModel;
    using System.Threading;
    using EasyStep.Data.Entities;


    public class RicherTextBox : RichTextBoxLineNumbers
    {

        public event EventHandler CursorPositionChanged;
        public event ValidateTextLineEventHandler LineValidation;
        public int PreviousLine = -1;
        [DebuggerStepThrough]
        protected virtual void OnCursorPositionChanged(EventArgs e)
        {
            if (CursorPositionChanged != null)
                CursorPositionChanged(this, e);
        }
        [DebuggerStepThrough]
        protected override void OnSelectionChanged(EventArgs e)
        {
            if (SelectionLength == 0)
                OnCursorPositionChanged(e);
            else
                base.OnSelectionChanged(e);
            PreviousLine = this.CurrentLine;
        }
        
        public int CurrentColumn
        {
            get { return CursorPosition.Column(this, SelectionStart); }
        }
  
        public int CurrentLine
        {
            get { return CursorPosition.Line(this, SelectionStart); }
        }
       
        public int CurrentPosition
        {
            get { return this.SelectionStart; }
        }
    
        public int SelectionEnd
        {
            get { return SelectionStart + SelectionLength; }
        }

        [DebuggerStepThrough]
        public class CursorPosition
        {
            [System.Runtime.InteropServices.DllImport("user32")]          
            public static extern int GetCaretPos(ref Point lpPoint);
           
            private static int GetCorrection(RichTextBox e, int index)
            {
                var pt1 = Point.Empty;
                GetCaretPos(ref pt1);
                var pt2 = e.GetPositionFromCharIndex(index);
                if (pt1 != pt2) return 1;
                return 0;
            }
          
            public static int Line(RichTextBox e, int index)
            {
                int correction = GetCorrection(e, index);
                return e.GetLineFromCharIndex(index) - correction + 1;
            }
           
            public static int Column(RichTextBox e, int index1)
            {
                int correction = GetCorrection(e, index1);
                var p = e.GetPositionFromCharIndex(index1 - correction);
                if (p.X == 1) return 1;
                p.X = 0;
                int index2 = e.GetCharIndexFromPosition(p);
                int col = index1 - index2 + 1;
                return col;
            }
        }


        private SyntaxSettings m_settings = new SyntaxSettings();
        private static bool m_bPaint = true;
        private string m_strLine = "";
        private int m_nContentLength = 0;
        private int m_nLineLength = 0;
        private int m_nLineStart = 0;
        private int m_nLineEnd = 0;
        private string m_strActionKeywords = "";
        private string m_strConstKeywords = "";
        private int m_nCurSelection = 0;

        /// <summary>
        /// The settings.
        /// </summary>
        public SyntaxSettings Settings
        {
            get { return m_settings; }
        }

        public ILog Log { get; set; }
        public List<int> lastTimeChecked;
        private string[] lines;

        public void ExecuteHighlighting(BackgroundWorker bws)
        {
            if (lines == null)
                return;

            string[] copy = lines;
            var amount = lines.Length;
            var done = new List<int>();
            if (lastTimeChecked == null) lastTimeChecked = new List<int>();
           
            for (int i = 0; i < amount; i++)
            {
                var s = (string)copy.GetValue(i);
                var hashCode = s.GetHashCode();
                if (lastTimeChecked.Count <= i || lastTimeChecked[i] != hashCode)
                {
                    if (bws!=null)
                        this.Invoke(new MethodInvoker(() =>
                        {
                            ProcessLineAsync(i,s, null);
                        }));
                    else
                        ProcessLineAsync(i, s, null);

                    done.Add(hashCode);
                    break;
                }
                else
                    done.Add(hashCode);
            }
            lastTimeChecked = done;            // done.Count==copy.Length ?null:done;            
        }

        /// <summary>
        /// WndProc
        /// </summary>
        /// <param name="m"></param>
        [DebuggerStepThrough]
        protected override void WndProc(ref System.Windows.Forms.Message m)
        {
            if (m.Msg == 0x00f)
            {
                if (m_bPaint)
                    base.WndProc(ref m);
                else
                    m.Result = IntPtr.Zero;
            }
            else
                base.WndProc(ref m);
        }

        /// <summary>
        /// OnTextChanged
        /// </summary>
        /// <param name="e"></param>
        protected override void OnTextChanged(EventArgs e)
        {
            base.OnTextChanged(e);
            lines = Lines;
            HiglightCurrentLine();
        }

        private void HiglightCurrentLine()
        {
            m_nContentLength = this.TextLength;

            int nCurrentSelectionStart = SelectionStart;
            int nCurrentSelectionLength = SelectionLength;

            m_bPaint = false;

            // Find the start of the current line.
            m_nLineStart = nCurrentSelectionStart;
            while ((m_nLineStart > 0) && (Text[m_nLineStart - 1] != '\n'))
                m_nLineStart--;
            // Find the end of the current line.
            m_nLineEnd = nCurrentSelectionStart;
            while ((m_nLineEnd < Text.Length) && (Text[m_nLineEnd] != '\n'))
                m_nLineEnd++;
            // Calculate the length of the line.
            m_nLineLength = m_nLineEnd - m_nLineStart;
            // Get the current line.
            m_strLine = Text.Substring(m_nLineStart, m_nLineLength);

            // Process this line.
            ProcessLine(true);
            m_bPaint = true;
        }
        public string GetLineTextTillCursor()
        {
            m_nContentLength = this.TextLength;

            int nCurrentSelectionStart = SelectionStart;
            int nCurrentSelectionLength = SelectionLength;

            m_bPaint = false;

            // Find the start of the current line.
            m_nLineStart = nCurrentSelectionStart;
            while ((m_nLineStart > 0) && (Text[m_nLineStart - 1] != '\n'))
                m_nLineStart--;
            // Find the end .
            m_nLineEnd = nCurrentSelectionStart + SelectionLength;
            // Calculate the length of the line.
            m_nLineLength = m_nLineEnd - m_nLineStart;
            // Get the current line.
            m_strLine = Text.Substring(m_nLineStart, m_nLineLength);
            m_bPaint = true;
            return m_strLine;
        }
        /// <summary>
        /// Process a line.
        /// </summary>
        private void ProcessLine(bool withValidation = true)
        {
            // Save the position and make the whole line black            
            int nPosition = SelectionStart;
            //SetSelectionColor(m_nLineStart,m_nLineLength,Color.Black);
            SelectionStart = m_nLineStart;
            SelectionLength = m_nLineLength;
            SelectionColor = Color.Black;

            var result = true;
            if (withValidation && LineValidation != null && !string.IsNullOrEmpty(m_strLine))
            {
                var e = new ValidateTextLineEventArgs() { Valid = true, lineText = m_strLine };
                LineValidation(this, e);

                if (!e.Valid)
                {
                    SelectionStart = m_nLineStart;
                    SelectionLength = m_nLineLength;
                    SelectionColor = Settings.NotValidLineColor;
                }
                result = e.Valid;
            }

            ProcessRegex(m_strActionKeywords, Settings.ActionKeywordColor);
            ProcessRegex(m_strConstKeywords, Settings.ConstantKeywordColor);
            // Process strings
            if (Settings.EnableStrings)
                ProcessRegex("\"[^\"\\\\\\r\\n]*(?:\\\\.[^\"\\\\\\r\\n]*)*\"", Settings.StringColor);
            // Process comments
            if (Settings.EnableComments && !string.IsNullOrEmpty(Settings.Comment))
                ProcessRegex(Settings.Comment + ".*$", Settings.CommentColor);
            SelectionStart = nPosition;
            SelectionLength = 0;
            SelectionColor = Color.Black;
            m_nCurSelection = nPosition;
        }

        private void ProcessLineAsync(int i, string textToCheck, Action<int, string> onProcessingComplete)
        {
            var startAt = GetFirstCharIndexFromLine(i);
            
            var length = textToCheck.Length;
            var validationResult = true;
            if (LineValidation != null)
            {
                var e = new ValidateTextLineEventArgs() { Valid = true, lineText = m_strLine };
                LineValidation(this, e);
                validationResult = e.Valid;
            }
            var vcolor = validationResult ? Color.Black : Settings.NotValidLineColor;
            var actions = ProcessRegex(m_strActionKeywords);
            var consts = ProcessRegex(m_strConstKeywords);

            int nPosition = SelectionStart;
            int nLength = SelectionLength;
            Color nColor = SelectionColor;
            m_bPaint = false;
            //hide selection
            User32.SendMessage(this.Handle, User32.Msgs.EM_HIDESELECTION, 1, 0);
            SelectionStart = startAt;
            SelectionLength = length;
            SelectionColor = vcolor;

            ColorizeRegExps(actions, Settings.ActionKeywordColor, startAt);
            ColorizeRegExps(consts, Settings.ConstantKeywordColor, startAt);
            //unhide selection
            User32.SendMessage(this.Handle, User32.Msgs.EM_HIDESELECTION, 0, 0);
            SelectionStart = nPosition;
            SelectionLength = nLength;
            SelectionColor = nColor;
            
            m_bPaint = true;
            if (onProcessingComplete!=null) onProcessingComplete(i, textToCheck);
        }
        private void ColorizeRegExps(IEnumerable<Match> items, Color color, int lineStart)
        {
            if (items == null) return;
            foreach (var item in items)
            {
                int nStart = lineStart + item.Index;
                int nLenght = item.Length;
                SelectionStart = nStart;
                SelectionLength = nLenght;
                SelectionColor = color;
            }
        }
        /// <summary>
        /// Process a regular expression.
        /// </summary>
        /// <param name="strRegex">The regular expression.</param>
        /// <param name="color">The color.</param>
        private void ProcessRegex(string strRegex, Color color)
        {
            Regex regKeywords = new Regex(strRegex, RegexOptions.IgnoreCase | RegexOptions.Compiled);
            Match regMatch;

            for (regMatch = regKeywords.Match(m_strLine); regMatch.Success; regMatch = regMatch.NextMatch())
            {
                if (regMatch.Length < 1) continue;
                int nStart = m_nLineStart + regMatch.Index;
                int nLenght = regMatch.Length;
                SelectionStart = nStart;
                SelectionLength = nLenght;
                SelectionColor = color;
            }
        }

        public IEnumerable<Match> ProcessRegex(string strRegex)
        {
            Regex regKeywords = new Regex(strRegex, RegexOptions.IgnoreCase | RegexOptions.Compiled);
            Match regMatch;
            for (regMatch = regKeywords.Match(m_strLine); regMatch.Success; regMatch = regMatch.NextMatch())
                if (regMatch.Length < 1) continue;
                else
                    yield return regMatch;
        }
        /// <summary>
        /// Compiles the keywords as a regular expression.
        /// </summary>
        public void CompileKeywords()
        {
            var sb = new StringBuilder();
            for (int i = 0; i < Settings.ActionKeywords.Count; i++)
            {
                string strKeyword = Settings.ActionKeywords[i];
                if (i == Settings.ActionKeywords.Count - 1)
                    sb.AppendFormat("\\b{0}\\b", strKeyword);
                else
                    sb.AppendFormat("\\b{0}\\b|", strKeyword);
            }
            m_strActionKeywords = sb.ToString();
            sb.Clear();
            for (int i = 0; i < Settings.ConstantKeywords.Count; i++)
            {
                string strKeyword = Settings.ConstantKeywords[i];
                if (i == Settings.ActionKeywords.Count - 1)
                    sb.AppendFormat("\\b{0}\\b", strKeyword);
                else
                    sb.AppendFormat("\\b{0}\\b|", strKeyword);
            }
            m_strConstKeywords = sb.ToString();
        }
        public bool ProcessAllLines(bool withValidation)
        {
            m_bPaint = false;
            int nStartPos = 0;
            int i = 0;
            int nOriginalPos = SelectionStart;
            while (i < Lines.Length)
            {
                m_strLine = Lines[i];
                m_nLineStart = nStartPos;
                m_nLineEnd = m_nLineStart + m_strLine.Length;

                //result = result & ProcessLine(withValidation);
                i++;
                nStartPos += m_strLine.Length + 1;
            }
            m_bPaint = true;
            return true;
        }
    }
    /// <summary>
    /// Class to store syntax objects in.
    /// </summary>
    public class SyntaxList
    {
        public List<string> m_rgList = new List<string>();
        public Color m_color = new Color();
    }

    /// <summary>
    /// Settings for the keywords and colors.
    /// </summary>
    public class SyntaxSettings
    {

        SyntaxList m_rgActions = new SyntaxList();
        SyntaxList m_rgConstants = new SyntaxList();
        string m_strComment = "";
        Color m_colorComment = Color.Green;
        Color m_colorString = Color.Gray;
        Color m_NotValidLineColor = Color.Red;
        bool m_bEnableComments = false;
        bool m_bEnableStrings = true;

        #region Properties
        /// <summary>
        /// A list containing all Action keywords.
        /// </summary>
        public List<string> ActionKeywords
        {
            get { return m_rgActions.m_rgList; }
        }
        /// <summary>
        /// The color of keywords.
        /// </summary>
        public Color ActionKeywordColor
        {
            get { return m_rgActions.m_color; }
            set { m_rgActions.m_color = value; }
        }
        /// <summary>
        /// A list containing all Constant keywords.
        /// </summary>
        public List<string> ConstantKeywords
        {
            get { return m_rgConstants.m_rgList; }
        }
        /// <summary>
        /// The color of Constant keywords.
        /// </summary>
        public Color ConstantKeywordColor
        {
            get { return m_rgConstants.m_color; }
            set { m_rgConstants.m_color = value; }
        }

        /// <summary>
        /// A string containing the comment identifier.
        /// </summary>
        public string Comment
        {
            get { return m_strComment; }
            set { m_strComment = value; }
        }
        /// <summary>
        /// The color of comments.
        /// </summary>
        public Color CommentColor
        {
            get { return m_colorComment; }
            set { m_colorComment = value; }
        }
        /// <summary>
        /// Enables processing of comments if set to true.
        /// </summary>
        public bool EnableComments
        {
            get { return m_bEnableComments; }
            set { m_bEnableComments = value; }
        }

        /// <summary>
        /// Enables processing of strings if set to true.
        /// </summary>
        public bool EnableStrings
        {
            get { return m_bEnableStrings; }
            set { m_bEnableStrings = value; }
        }
        /// <summary>
        /// The color of strings.
        /// </summary>
        public Color StringColor
        {
            get { return m_colorString; }
            set { m_colorString = value; }
        }
        /// <summary>
        /// The color of integers.
        /// </summary>
        public Color NotValidLineColor
        {
            get { return m_NotValidLineColor; }
            set { m_NotValidLineColor = value; }
        }
        #endregion
    }

    public class ValidateTextLineEventArgs : EventArgs
    {
        public bool Valid { get; set; }
        public string lineText { get; set; }
    }
    public delegate void ValidateTextLineEventHandler(object sender, ValidateTextLineEventArgs e);


    [ToolboxBitmap(typeof(RichTextBox))]
    public class RichTextBoxLineNumbers : RichTextBox
    {
        private User32.CHARFORMAT charFormat;
        private IntPtr lParam1;

        private int _savedScrollLine;
        private int _savedSelectionStart;
        private int _savedSelectionEnd;
        private Pen _borderPen;
        private System.Drawing.StringFormat _stringDrawingFormat;
        private System.Security.Cryptography.HashAlgorithm alg;   // used for comparing text values

        public RichTextBoxLineNumbers()
        {
            charFormat = new User32.CHARFORMAT()
            {
                cbSize = Marshal.SizeOf(typeof(User32.CHARFORMAT)),
                szFaceName = new char[32]
            };

            lParam1 = Marshal.AllocCoTaskMem(charFormat.cbSize);

            // defaults
            NumberFont = new System.Drawing.Font("Consolas",
                                                9.75F,
                                                System.Drawing.FontStyle.Regular,
                                                System.Drawing.GraphicsUnit.Point, ((byte)(0)));

            NumberColor = Color.FromName("DarkGray");
            NumberLineCounting = LineCounting.CRLF;
            NumberAlignment = StringAlignment.Center;
            NumberBorder = SystemColors.ControlDark;
            NumberBorderThickness = 1;
            NumberPadding = 2;
            NumberBackground1 = SystemColors.ControlLight;
            NumberBackground2 = SystemColors.Window;
            SetStringDrawingFormat();

            alg = System.Security.Cryptography.SHA1.Create();
        }

        ~RichTextBoxLineNumbers()
        {
            // Free the allocated memory
            Marshal.FreeCoTaskMem(lParam1);
        }

        private void SetStringDrawingFormat()
        {
            _stringDrawingFormat = new System.Drawing.StringFormat
            {
                Alignment = StringAlignment.Center,
                LineAlignment = NumberAlignment,
                Trimming = StringTrimming.None,
            };
        }


        protected override void OnTextChanged(EventArgs e)
        {
            NeedRecomputeOfLineNumbers();
            base.OnTextChanged(e);
        }

        public void BeginUpdate()
        {
            User32.SendMessage(this.Handle, (int)User32.Msgs.WM_SETREDRAW, 0, IntPtr.Zero);
        }

        public void EndUpdate()
        {
            User32.SendMessage(this.Handle, (int)User32.Msgs.WM_SETREDRAW, 1, IntPtr.Zero);
        }


        public IntPtr BeginUpdateAndSuspendEvents()
        {
            // Stop redrawing:
            User32.SendMessage(this.Handle, (int)User32.Msgs.WM_SETREDRAW, 0, IntPtr.Zero);
            // Stop sending of events:
            IntPtr eventMask = User32.SendMessage(this.Handle, User32.Msgs.EM_GETEVENTMASK, 0, IntPtr.Zero);

            return eventMask;
        }

        public void EndUpdateAndResumeEvents(IntPtr eventMask)
        {
            // turn on events
            User32.SendMessage(this.Handle, User32.Msgs.EM_SETEVENTMASK, 0, eventMask);
            // turn on redrawing
            User32.SendMessage(this.Handle, User32.Msgs.WM_SETREDRAW, 1, IntPtr.Zero);
            NeedRecomputeOfLineNumbers();
            this.Invalidate();
        }

        public void GetSelection(out int start, out int end)
        {
            User32.SendMessageRef(this.Handle, (int)User32.Msgs.EM_GETSEL, out start, out end);
        }

        public void SetSelection(int start, int end)
        {
            User32.SendMessage(this.Handle, (int)User32.Msgs.EM_SETSEL, start, end);
        }

        public void BeginUpdateAndSaveState()
        {
            User32.SendMessage(this.Handle, (int)User32.Msgs.WM_SETREDRAW, 0, IntPtr.Zero);
            // save scroll position
            _savedScrollLine = FirstVisibleDisplayLine;

            // save selection
            GetSelection(out _savedSelectionStart, out _savedSelectionEnd);
        }

        public void EndUpdateAndRestoreState()
        {
            // restore scroll position
            int Line1 = FirstVisibleDisplayLine;
            Scroll(_savedScrollLine - Line1);

            // restore the selection/caret
            SetSelection(_savedSelectionStart, _savedSelectionEnd);

            // allow redraw
            User32.SendMessage(this.Handle, (int)User32.Msgs.WM_SETREDRAW, 1, IntPtr.Zero);

            // explicitly ask for a redraw?
            Refresh();
        }

        private String _sformat;
        private int _ndigits;
        private int _lnw = -1;
        private int LineNumberWidth
        {
            get
            {
                if (_lnw > 0) return _lnw;
                if (NumberLineCounting == LineCounting.CRLF)
                {
                    _ndigits = (CharIndexForTextLine.Length == 0)
                        ? 1
                        : (int)(1 + Math.Log((double)CharIndexForTextLine.Length, 10));
                }
                else
                {
                    int n = GetDisplayLineCount();
                    _ndigits = (n == 0)
                        ? 1
                        : (int)(1 + Math.Log((double)n, 10));
                }
                var s = new String('0', _ndigits);
                var b = new Bitmap(400, 400); // in pixels
                var g = Graphics.FromImage(b);
                SizeF size = g.MeasureString(s, NumberFont);
                g.Dispose();
                _lnw = NumberPadding * 2 + 4 + (int)(size.Width + 0.5 + NumberBorderThickness);
                _sformat = "{0:D" + _ndigits + "}";
                return _lnw;
            }
        }


        public bool _lineNumbers = true;
        public bool ShowLineNumbers
        {
            get
            {
                return _lineNumbers;
            }
            set
            {
                if (value == _lineNumbers) return;
                SetLeftMargin(value ? LineNumberWidth + Margin.Left : Margin.Left);
                _lineNumbers = value;
                User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
            }
        }

        private void NeedRecomputeOfLineNumbers()
        {
            //System.Console.WriteLine("Need Recompute of line numbers...");
            _CharIndexForTextLine = null;
            _Text2 = null;
            _lnw = -1;

            if (_paintingDisabled) return;

            User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
        }

        private Font _NumberFont;
        public Font NumberFont
        {
            get { return _NumberFont; }
            set
            {
                if (_NumberFont == value) return;
                _lnw = -1;
                _NumberFont = value;
                User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
            }
        }

        private LineCounting _NumberLineCounting;
        public LineCounting NumberLineCounting
        {
            get { return _NumberLineCounting; }
            set
            {
                if (_NumberLineCounting == value) return;
                _lnw = -1;
                _NumberLineCounting = value;
                User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
            }
        }

        private StringAlignment _NumberAlignment;
        public StringAlignment NumberAlignment
        {
            get { return _NumberAlignment; }
            set
            {
                if (_NumberAlignment == value) return;
                _NumberAlignment = value;
                SetStringDrawingFormat();
                User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
            }
        }

        private Color _NumberColor;
        public Color NumberColor
        {
            get { return _NumberColor; }
            set
            {
                if (_NumberColor.ToArgb() == value.ToArgb()) return;
                _NumberColor = value;
                User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
            }
        }

        private bool _NumberLeadingZeroes;
        public bool NumberLeadingZeroes
        {
            get { return _NumberLeadingZeroes; }
            set
            {
                if (_NumberLeadingZeroes == value) return;
                _NumberLeadingZeroes = value;
                User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
            }
        }

        private Color _NumberBorder;
        public Color NumberBorder
        {
            get { return _NumberBorder; }
            set
            {
                if (_NumberBorder.ToArgb() == value.ToArgb()) return;
                _NumberBorder = value;
                NewBorderPen();
                User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
            }
        }

        private int _NumberPadding;
        public int NumberPadding
        {
            get { return _NumberPadding; }
            set
            {
                if (_NumberPadding == value) return;
                _lnw = -1;
                _NumberPadding = value;
                User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
            }
        }

        public Single _NumberBorderThickness;
        public Single NumberBorderThickness
        {
            get { return _NumberBorderThickness; }
            set
            {
                if (_NumberBorderThickness == value) return;
                _lnw = -1;
                _NumberBorderThickness = value;
                NewBorderPen();
                User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
            }
        }

        private Color _NumberBackground1;
        public Color NumberBackground1
        {
            get { return _NumberBackground1; }
            set
            {
                if (_NumberBackground1.ToArgb() == value.ToArgb()) return;
                _NumberBackground1 = value;
                User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
            }
        }

        private Color _NumberBackground2;
        public Color NumberBackground2
        {
            get { return _NumberBackground2; }
            set
            {
                if (_NumberBackground2.ToArgb() == value.ToArgb()) return;
                _NumberBackground2 = value;
                User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
            }
        }


        private bool _paintingDisabled;
        public void SuspendLineNumberPainting()
        {
            _paintingDisabled = true;
        }
        public void ResumeLineNumberPainting()
        {
            _paintingDisabled = false;
        }


        private void NewBorderPen()
        {
            _borderPen = new Pen(NumberBorder);
            _borderPen.Width = NumberBorderThickness;
            _borderPen.SetLineCap(LineCap.Round, LineCap.Round, DashCap.Round);
        }



        private DateTime _lastMsgRecd = new DateTime(1901, 1, 1);
        [DebuggerStepThrough]
        protected override void WndProc(ref Message m)
        {
            bool handled = false;
            switch (m.Msg)
            {
                case (int)User32.Msgs.WM_PAINT:
                    //System.Console.WriteLine("{0}", User32.Mnemonic(m.Msg));
                    //System.Console.Write(".");
                    if (_paintingDisabled) return;
                    if (_lineNumbers)
                    {
                        base.WndProc(ref m);
                        this.PaintLineNumbers();
                        handled = true;
                    }
                    break;

                case (int)User32.Msgs.WM_CHAR:
                    // the text is being modified
                    NeedRecomputeOfLineNumbers();
                    break;

                //                 case (int)User32.Msgs.EM_POSFROMCHAR:
                //                 case (int)User32.Msgs.WM_GETDLGCODE:
                //                 case (int)User32.Msgs.WM_ERASEBKGND:
                //                 case (int)User32.Msgs.OCM_COMMAND:
                //                 case (int)User32.Msgs.OCM_NOTIFY:
                //                 case (int)User32.Msgs.EM_CHARFROMPOS:
                //                 case (int)User32.Msgs.EM_LINEINDEX:
                //                 case (int)User32.Msgs.WM_NCHITTEST:
                //                 case (int)User32.Msgs.WM_SETCURSOR:
                //                 case (int)User32.Msgs.WM_KEYUP:
                //                 case (int)User32.Msgs.WM_KEYDOWN:
                //                 case (int)User32.Msgs.WM_MOUSEMOVE:
                //                 case (int)User32.Msgs.WM_MOUSEACTIVATE:
                //                 case (int)User32.Msgs.WM_NCMOUSEMOVE:
                //                 case (int)User32.Msgs.WM_NCMOUSEHOVER:
                //                 case (int)User32.Msgs.WM_NCMOUSELEAVE:
                //                 case (int)User32.Msgs.WM_NCLBUTTONDOWN:
                //                     break;
                //
                //                   default:
                //                       // divider
                //                       var now = DateTime.Now;
                //                       if ((now - _lastMsgRecd) > TimeSpan.FromMilliseconds(850))
                //                           System.Console.WriteLine("------------ {0}", now.ToString("G"));
                //                       _lastMsgRecd = now;
                //
                //                       System.Console.WriteLine("{0}", User32.Mnemonic(m.Msg));
                //                       break;
            }

            if (!handled)
                base.WndProc(ref m);
        }


        int _lastWidth = 0;
        [DebuggerStepThrough]
        private void PaintLineNumbers()
        {
            //System.Console.WriteLine(">> PaintLineNumbers");
            // To reduce flicker, double-buffer the output

            if (_paintingDisabled) return;

            int w = LineNumberWidth;
            if (w != _lastWidth)
            {
                //System.Console.WriteLine("  WIDTH change {0} != {1}", _lastWidth, w);
                SetLeftMargin(w + Margin.Left);
                _lastWidth = w;
                // Don't bother painting line numbers - the margin isn't wide enough currently.
                // Ask for a new paint, and paint them next time round.
                User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
                return;
            }

            Bitmap buffer = new Bitmap(w, this.Bounds.Height);
            Graphics g = Graphics.FromImage(buffer);

            Brush forebrush = new SolidBrush(NumberColor);
            var rect = new Rectangle(0, 0, w, this.Bounds.Height);

            bool wantDivider = NumberBackground1.ToArgb() == NumberBackground2.ToArgb();
            Brush backBrush = (wantDivider)
                ? (Brush)new SolidBrush(NumberBackground2)
                : SystemBrushes.Window;

            g.FillRectangle(backBrush, rect);

            int n = (NumberLineCounting == LineCounting.CRLF)
                ? NumberOfVisibleTextLines
                : NumberOfVisibleDisplayLines;

            int first = (NumberLineCounting == LineCounting.CRLF)
                ? FirstVisibleTextLine
                : FirstVisibleDisplayLine + 1;

            int py = 0;
            int w2 = w - 2 - (int)NumberBorderThickness;
            LinearGradientBrush brush;
            Pen dividerPen = new Pen(NumberColor);

            for (int i = 0; i <= n; i++)
            {
                int ix = first + i;
                int c = (NumberLineCounting == LineCounting.CRLF)
                    ? GetCharIndexForTextLine(ix)
                    : GetCharIndexForDisplayLine(ix) - 1;

                var p = GetPosFromCharIndex(c + 1);

                Rectangle r4 = Rectangle.Empty;

                if (i == n) // last line?
                {
                    if (this.Bounds.Height <= py) continue;
                    r4 = new Rectangle(1, py, w2, this.Bounds.Height - py);
                }
                else
                {
                    if (p.Y <= py) continue;
                    r4 = new Rectangle(1, py, w2, p.Y - py);
                }

                if (wantDivider)
                {
                    if (i != n)
                        g.DrawLine(dividerPen, 1, p.Y + 1, w2, p.Y + 1); // divider line
                }
                else
                {
                    // new brush each time for gradient across variable rect sizes
                    brush = new LinearGradientBrush(r4,
                                                     NumberBackground1,
                                                     NumberBackground2,
                                                     LinearGradientMode.Vertical);
                    g.FillRectangle(brush, r4);
                }

                if (NumberLineCounting == LineCounting.CRLF) ix++;

                // conditionally slide down
                if (NumberAlignment == StringAlignment.Near)
                    rect.Offset(0, 3);

                var s = (NumberLeadingZeroes) ? String.Format(_sformat, ix) : ix.ToString();
                g.DrawString(s, NumberFont, forebrush, r4, _stringDrawingFormat);
                py = p.Y;
            }

            if (NumberBorderThickness != 0.0)
            {
                int t = (int)(w - (NumberBorderThickness + 0.5) / 2) - 1;
                g.DrawLine(_borderPen, t, 0, t, this.Bounds.Height);
                //g.DrawLine(_borderPen, w-2, 0, w-2, this.Bounds.Height);
            }

            // paint that buffer to the screen
            Graphics g1 = this.CreateGraphics();
            g1.DrawImage(buffer, new Point(0, 0));
            g1.Dispose();
            g.Dispose();
        }



        private int GetCharIndexFromPos(int x, int y)
        {
            var p = new User32.POINTL { X = x, Y = y };
            int rawSize = Marshal.SizeOf(typeof(User32.POINTL));
            IntPtr lParam = Marshal.AllocHGlobal(rawSize);
            Marshal.StructureToPtr(p, lParam, false);
            int r = User32.SendMessage(this.Handle, (int)User32.Msgs.EM_CHARFROMPOS, 0, lParam);
            Marshal.FreeHGlobal(lParam);
            return r;
        }


        private Point GetPosFromCharIndex(int ix)
        {
            int rawSize = Marshal.SizeOf(typeof(User32.POINTL));
            IntPtr wParam = Marshal.AllocHGlobal(rawSize);
            int r = User32.SendMessage(this.Handle, (int)User32.Msgs.EM_POSFROMCHAR, (int)wParam, ix);

            User32.POINTL p1 = (User32.POINTL)Marshal.PtrToStructure(wParam, typeof(User32.POINTL));

            Marshal.FreeHGlobal(wParam);
            var p = new Point { X = p1.X, Y = p1.Y };
            return p;
        }


        private int GetLengthOfLineContainingChar(int charIndex)
        {
            //it's doesn't work -isn't it?
            int len = User32.SendMessage(this.Handle, (int)User32.Msgs.EM_LINELENGTH, charIndex, IntPtr.Zero);
            return len;
        }
        public int GetLastCharIndexForLineContainingChar(int charIndex)
        {
            int len = User32.SendMessage(this.Handle, (int)User32.Msgs.EM_LINELENGTH, charIndex, IntPtr.Zero);
            var lineNr = this.GetLineFromCharIndex(charIndex);
            int pos = User32.SendMessage(this.Handle, (int)User32.Msgs.EM_LINEINDEX, lineNr, IntPtr.Zero);
            return len + pos;
        }

        private int GetLineFromChar(int charIndex)
        {
            return User32.SendMessage(this.Handle, (int)User32.Msgs.EM_LINEFROMCHAR, charIndex, 0);
        }

        private int GetCharIndexForDisplayLine(int line)
        {
            return User32.SendMessage(this.Handle, (int)User32.Msgs.EM_LINEINDEX, line, 0);
        }

        private int GetDisplayLineCount()
        {
            return User32.SendMessage(this.Handle, (int)User32.Msgs.EM_GETLINECOUNT, 0, 0);
        }


        /// <summary>
        ///   Sets the color of the characters in the given range.
        /// </summary>
        ///
        /// <remarks>
        /// Calling this is equivalent to calling
        /// <code>
        ///   richTextBox.Select(start, end-start);
        ///   this.richTextBox1.SelectionColor = color;
        /// </code>
        /// ...but without the error and bounds checking.
        /// </remarks>
        ///
        public void SetSelectionColor(int start, int end, System.Drawing.Color color)
        {
            User32.SendMessage(this.Handle, (int)User32.Msgs.EM_SETSEL, start, end);

            charFormat.dwMask = 0x40000000;
            charFormat.dwEffects = 0;
            charFormat.crTextColor = System.Drawing.ColorTranslator.ToWin32(color);

            Marshal.StructureToPtr(charFormat, lParam1, false);
            User32.SendMessage(this.Handle, (int)User32.Msgs.EM_SETCHARFORMAT, User32.SCF_SELECTION, lParam1);
        }


        private void SetLeftMargin(int widthInPixels)
        {
            User32.SendMessage(this.Handle, (int)User32.Msgs.EM_SETMARGINS, User32.EC_LEFTMARGIN,
                               widthInPixels);
        }

        public Tuple<int, int> GetMargins()
        {
            int r = User32.SendMessage(this.Handle, (int)User32.Msgs.EM_GETMARGINS, 0, 0);
            return Tuple.New(r & 0x0000FFFF, (int)((r >> 16) & 0x0000FFFF));
        }

        public void Scroll(int delta)
        {
            User32.SendMessage(this.Handle, (int)User32.Msgs.EM_LINESCROLL, 0, delta);
        }


        private int FirstVisibleDisplayLine
        {
            get
            {
                return User32.SendMessage(this.Handle, (int)User32.Msgs.EM_GETFIRSTVISIBLELINE, 0, 0);
            }
            set
            {
                // scroll
                int current = FirstVisibleDisplayLine;
                int delta = value - current;
                User32.SendMessage(this.Handle, (int)User32.Msgs.EM_LINESCROLL, 0, delta);
            }
        }

        private int NumberOfVisibleDisplayLines
        {
            get
            {
                int topIndex = this.GetCharIndexFromPosition(new System.Drawing.Point(1, 1));
                int bottomIndex = this.GetCharIndexFromPosition(new System.Drawing.Point(1, this.Height - 1));
                int topLine = this.GetLineFromCharIndex(topIndex);
                int bottomLine = this.GetLineFromCharIndex(bottomIndex);
                int n = bottomLine - topLine + 1;
                return n;
            }
        }

        private int FirstVisibleTextLine
        {
            get
            {
                int c = GetCharIndexFromPos(1, 1);
                for (int i = 0; i < CharIndexForTextLine.Length; i++)
                {
                    if (c < CharIndexForTextLine[i]) return i;
                }
                return CharIndexForTextLine.Length;
            }
        }

        private int LastVisibleTextLine
        {
            get
            {
                int c = GetCharIndexFromPos(1, this.Bounds.Y + this.Bounds.Height);
                for (int i = 0; i < CharIndexForTextLine.Length; i++)
                {
                    if (c < CharIndexForTextLine[i]) return i;
                }
                return CharIndexForTextLine.Length;
            }
        }

        private int NumberOfVisibleTextLines
        {
            get
            {
                return LastVisibleTextLine - FirstVisibleTextLine;
            }
        }


        public int FirstVisibleLine
        {
            get
            {
                if (this.NumberLineCounting == LineCounting.CRLF)
                    return FirstVisibleTextLine;
                else
                    return FirstVisibleDisplayLine;
            }
        }

        public int NumberOfVisibleLines
        {
            get
            {
                if (this.NumberLineCounting == LineCounting.CRLF)
                    return NumberOfVisibleTextLines;
                else
                    return NumberOfVisibleDisplayLines;
            }
        }

        private int GetCharIndexForTextLine(int ix)
        {
            if (ix >= CharIndexForTextLine.Length) return 0;
            if (ix < 0) return 0;
            return CharIndexForTextLine[ix];
        }



        // The char index is expensive to compute.

        private int[] _CharIndexForTextLine;
        private int[] CharIndexForTextLine
        {
            get
            {
                if (_CharIndexForTextLine == null)
                {
                    var list = new List<int>();
                    int ix = 0;
                    foreach (var c in Text2)
                    {
                        if (c == '\n') list.Add(ix);
                        ix++;
                    }
                    _CharIndexForTextLine = list.ToArray();
                }
                return _CharIndexForTextLine;
            }

        }


        private String _Text2;
        private String Text2
        {
            get
            {
                if (_Text2 == null)
                    _Text2 = this.Text;
                return _Text2;
            }
        }

        private bool CompareHashes(byte[] a, byte[] b)
        {
            if (a.Length != b.Length) return false;
            for (int i = 0; i < a.Length; i++)
            {
                if (a[i] != b[i]) return false;
            }
            return true;  // they are equal
        }



        public enum LineCounting
        {
            CRLF,
            AsDisplayed
        }

    }



    public static class Tuple
    {
        // Allows Tuple.New(1, "2") instead of new Tuple<int, string>(1, "2")
        public static Tuple<T1, T2> New<T1, T2>(T1 v1, T2 v2)
        {
            return new Tuple<T1, T2>(v1, v2);
        }
    }

    public class Tuple<T1, T2>
    {
        public Tuple(T1 v1, T2 v2)
        {
            V1 = v1;
            V2 = v2;
        }

        public T1 V1 { get; set; }
        public T2 V2 { get; set; }
    }

}


